In [1]:
import keras
from keras.datasets import mnist
import numpy as np
import math  
from matplotlib import pyplot as plt
from skimage.measure import compare_mse
import PIL
import cv2
from functools import reduce
import random as r
Using TensorFlow backend.
In [0]:
class Image(object):
  def __init__(self, path):
    self.path = path

    self.rgb_image = None
    self.bgr_image = None
    self.gray_image = None


  def read_image(self, return_image = False):
    self.rgb_image = plt.imread(self.path)
    if return_image:
      return self.rgb_image


  def bgr(self, return_image = False):
    self.bgr_image = np.flip(plt.imread(self.path), 2)
    if return_image:
      return self.bgr_image


  def gray(self, return_image = False):
    self.gray_image = cv2.cvtColor(plt.imread(self.path), cv2.COLOR_RGB2GRAY)
    if return_image:
      return self.gray_image

  
  @staticmethod
  def show(image, title = 'image'):
    if len(image.shape) == 3:
      plt.imshow(image)
    else:
      plt.imshow(image, cmap = 'gray')

    plt.title(title)

  
  @staticmethod
  def show_all(image_list, title_list):
    assert len(image_list) == len(title_list), "Incompatible lengths of lists!"
    N = len(image_list)
    plt.figure(figsize=[20, 20])

    for i in range(N):
      plt.subplot(1, N, i + 1)
      Image.show(image_list[i], title_list[i])
    
    plt.show()
In [0]:
class ManipulateImage:

  @staticmethod
  def flip(image, axis):
    return np.flip(image, axis)  

  @staticmethod
  def rotate(image, angle):
    image_center = tuple(np.array(image.shape[1::-1]) / 2)
    rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
    result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
    return result
  
  @staticmethod 
  def gaussian_blur(img, ksize, sigma):  
    dst = cv2.GaussianBlur(img,ksize,sigma,cv2.BORDER_DEFAULT)
    return dst

  @staticmethod 
  def median_blur(img, ksize):  
    dst = cv2.medianBlur(img, ksize)
    return dst

  @staticmethod 
  def my_median_blur(img, ksize): 
    result = np.copy(img)
    m = ksize//2
    for i in range(m, img.shape[0] - m):
      for j in range(m, img.shape[1] - m):
        for c in range(3):
          sub_img = img[i:i+ksize, j:j+ksize, c]
          median = np.median(sub_img)
          result[i][j][c] = median
    return result

  @staticmethod
  def crop(img, start, h, w):
    if start[0]+h >= img.shape[0]:
      h = img.shape[0] - start[0] - 1
    if start[1]+w >= img.shape[1]:
      w = img.shape[1] - start[1] - 1
    return img[start[0]:start[0]+h, start[1]:start[1]+w]
  
  @staticmethod
  def zoom(img, zoom_multiplier):
    w = img.shape[1]
    h = img.shape[0]
    width = int(w * 1/zoom_multiplier)
    height = int(h * 1/zoom_multiplier)
    dim = (w, h)
    new_img = ManipulateImage.crop(img, ((h//2) - (height//2), (w//2) - (width//2)), width, height)
    # resize image
    resized = cv2.resize(new_img, dim, interpolation = cv2.INTER_AREA)
    return resized
In [0]:
class RandomAugmentation(ManipulateImage):
  def generate_augmentations(img, count, probabilities=0.5): 
    import random as r
    
    def choose_subgroup(arr, probability):
      subgroup = []
      probs = [r.uniform(0, 1) < probability for i in range(6)]
      i = 0
      for boolean in probs:
        if boolean:
          subgroup.append(i)
          if i==4:
            break
        i += 1        
      return subgroup

    def augment_by_subgroup(img, sub):
      functions = [ManipulateImage.flip, ManipulateImage.rotate, ManipulateImage.gaussian_blur, ManipulateImage.median_blur, ManipulateImage.zoom, ManipulateImage.crop]
      current_img = np.copy(img)
      if 0 in sub:
        random_axis = r.randint(0, 2)
        current_img = (functions[0](current_img, random_axis))

      if 1 in sub:
        random_angle = r.randint(0, 180)
        current_img = (functions[1](current_img, random_angle))

      if 2 in sub:
        ksize = 2 * r.randint(1, 3) + 1
        sigma = r.randint(1, 15)
        current_img = (functions[2](current_img, (ksize, ksize), sigma))
      
      if 3 in sub:
        ksize = 2 * r.randint(1, 3) + 1
        current_img = (functions[3](current_img, ksize))
      
      if 4 in sub:
        zoom_multiplier = r.randint(1, 2)
        current_img = (functions[4](current_img, zoom_multiplier))    
      
      if 5 in sub:
        start = (r.randint(0,img.shape[0]//5),r.randint(0,img.shape[1]//5))
        h = r.randint(img.shape[0]//1.5, img.shape[0])
        current_img = (functions[5](current_img, start, h, h))
      return current_img
    
    augmentations = []
    for i in range(count):
      subgroup = choose_subgroup([0, 1, 2, 3, 4, 5], probabilities)
      augmentations.append(augment_by_subgroup(img, subgroup)) 
    return augmentations
In [29]:
(x_train, y_train), (x_test, y_test) = mnist.load_data()


def print_digits(examples, labels):
  # Prints some sorted ten digit images from a large dataset.

  count = 0
  ten_examples, ten_labels = list(), list()

  for i in range(len(labels)):
    if count > 9:
      break

    if labels[i] == count:
      count += 1
      ten_examples.append(examples[i])
      ten_labels.append(labels[i])

  Image.show_all(ten_examples, ten_labels)


print("Train:")
print_digits(x_train, y_train)

print("\n\nTest:")
print_digits(x_test, y_test)
Train:

Test:
In [30]:
def crop_img(gray_img):
  # Crops a given gray image according to its bounderies,
  # Returns the cropped image.

  ret, thresh = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY)
  contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
  
  contour_areas = list(map(lambda cnt: cv2.contourArea(cnt), contours))
  max_area_cnt = list(filter(lambda cnt: cv2.contourArea(cnt) == np.max(contour_areas), contours))[0]

  peri = cv2.arcLength(max_area_cnt, True)
  approx = cv2.approxPolyDP(max_area_cnt, 0.02 * peri, True)
  x, y, w, h = cv2.boundingRect(approx)

  return gray_img[y:y+h, x:x+w] 


def crop_images(img_lst):
  # Crops a given list of images,
  # Returns the cropped images as a new list.
  return list(map(lambda img: crop_img(img), img_lst))


cropped_x_train = crop_images(x_train)
cropped_x_test = crop_images(x_test)

print("Cropped train:")
print_digits(cropped_x_train, y_train)

print("\n\nCropped test:")
print_digits(cropped_x_test, y_test)
Cropped train:

Cropped test:
In [0]:
def list_images_by_label(tag_label, examples, labels):
  # Returns a list of images correspinding with the given label out of 
  # a given dataset of examples.

  new_lst = list()
  for i in range(len(labels)):
    if labels[i] == tag_label:
      new_lst.append(examples[i])

  return new_lst


labels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Defining two lists of lists of sorted images according to its labels.

sorted_digit_train_images = list(map(lambda label: list_images_by_label(label, cropped_x_train, y_train), labels))
#[print(len(lst)) for lst in sorted_digit_train_images]

sorted_digit_test_images = list(map(lambda label: list_images_by_label(label, cropped_x_test, y_test), labels))
#[print(len(lst)) for lst in sorted_digit_test_images]


# Globals
n = 101
m = 6000
number_of_digits = 3
In [0]:
def convert_number_to_digits(number):
  # Converts a given number to its digits,
  # Returns the number's digits as a list.
  
  temp = number
  digits = list()

  while temp > 0:
    digits.insert(0, int(temp % 10))
    temp = int(temp / 10)

  return digits


def pad_lst_by_zeroes(lst, n):
  # Pads a given list with zeroes to achieve a given number of elements- n.
  lst = list(lst)
  if n > len(lst):
    number_of_zeroes = n - len(lst)
    for i in range(number_of_zeroes):
      lst.insert(0, 0)
  return lst


def remove_array(outer_arr, inner_arr):
  # For ndarrays inside anoher array.

    index = 0
    size = len(outer_arr)
    while index != size and not np.array_equal(outer_arr[index], inner_arr):
        index += 1
    if index != size:
        outer_arr.pop(index)
    else:
        raise ValueError('array not found in list.')


def paste_images_horizontally(images_lst, horizontal_gap, vertical_gap, padding):
  # Pastes images horizontally into a new image, by a given list of images
  # and the spaces between the images,
  # Returns the new image that created by concatinating all the images in the given list

  total_width = images_lst[0].shape[1] + images_lst[1].shape[1] + images_lst[2].shape[1] + horizontal_gap * 2 + padding * (len(images_lst)-1)
  max_height = max(images_lst[0].shape[0], images_lst[1].shape[0], images_lst[2].shape[0]) + vertical_gap * 2
  new_img = np.zeros((max_height, total_width))
  
  positions = [vertical_gap, horizontal_gap]
  for img in images_lst:
    h = img.shape[0]
    w = img.shape[1]
    new_img[positions[0]:positions[0]+h, positions[1]:positions[1] + w] = img
    positions[1] += (w + padding)
  
  return new_img


def number_img_boundaries(number_gray_img):
  # Creates a new image contains the given image and it's bounderies,
  # Returns this new image.

  global number_of_digits
  new_img = np.zeros(number_gray_img.shape)

  temp_gray_image = cv2.normalize(number_gray_img, None, alpha=0, beta=1,
                        norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8UC1)
  
  ret, thresh = cv2.threshold(temp_gray_image, 0, 255, cv2.THRESH_BINARY)
  thresh = (thresh * 255).astype(np.uint8)

  contours, hierarchy = cv2.findContours(thresh,
                                    cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
  cv2.drawContours(new_img, contours, -1, (255, 255, 255), 1)

  return new_img
In [0]:
def create_new_img_examples(number_lst, sorted_digit_images):
  """
  Creates m = 6000 different examples that represents a specific number, 
  which given by a list lst that includes three images of three digits.
  Returns the m = 6000 images dataset of the corresponding number.
  """
  global m
  lsb0 = number_lst[0]
  lsb1 = number_lst[1]
  lsb2 = number_lst[2]

  combinations = []
  for num_image0 in sorted_digit_images[lsb0]:
    for num_image1 in sorted_digit_images[lsb1]:
      for num_image2 in sorted_digit_images[lsb2]:
        if len(combinations) == m:
          return combinations
        combinations.append([num_image0, num_image1, num_image2])
  return combinations


def compose_labels_by_lst(sorted_digit_images):
  # Composes a dataset - list of m = 6000 uniqe image examples, 
  # for each number between 0 to n.
  # Returns a tuple includes a list of the results, and a list of the corresponding labels
  # sorted_digit_images is a list of sorted lists of digit images (sorted by digit labels)

  global n, m, number_of_digits

  new_sorted_examples_as_lists = list()
  new_sorted_sampples, new_labels = list(), list()

  for number in range(0, n, 1):
    digits = convert_number_to_digits(number)
    digits = pad_lst_by_zeroes(digits, number_of_digits)
    number_examples = create_new_img_examples(digits, sorted_digit_images)
    new_sorted_examples_as_lists.append(number_examples)
  for number in range(n):
    for i in range(m):
      current_example = paste_images_horizontally(new_sorted_examples_as_lists[number][i], 5, 5, 3)
      new_sorted_sampples.append(current_example)
      new_labels.append(number)
    
  return new_sorted_sampples, new_labels
In [0]:
new_examples, new_labels = compose_labels_by_lst(sorted_digit_train_images)
In [11]:
for i in range(0, 600000, 6000):
  Image.show_all(new_examples[i:i+5], new_labels[i:i+5])
In [34]:
temp_img = number_img_boundaries(new_examples[600000])
Image.show(temp_img)
In [35]:
# AUGMENTATIONS OF 3 DIGIT NUMBERS
number_examples = []
for i in range(20):
  index = r.randint(0, 599999)
  img = cv2.cvtColor(new_examples[index].astype(np.uint8),cv2.COLOR_GRAY2RGB)
  number_examples.append(img)
  aug = RandomAugmentation.generate_augmentations(img, 4, 0.5)
  for img2 in aug:
    number_examples.append(img2)

for i in range(0, 100, 5):
  Image.show_all(number_examples[i:i+5], ["" for i in range(5)])


# flip completely ruins the images
# rotate will work only until 90 and -90 degrees
# zoom is ok 
# crop can crop out one number ruining the whole point of a 3-digit number
# blur is ok
In [0]:
#Part 2
In [0]:
class Shapes:
  def __init__(self, path):
    import random as r

  def load_data(self):
    # return (x_train, y_train), (x_test, y_test)
    pass
  @staticmethod
  def create_ellipse():
    center = (r.randint(20, 30), r.randint(20, 30))
    min_center = min(center)
    ellipse = np.zeros((50, 50, 3),np.uint8)
    axesLength = (r.randint(5, min_center), r.randint(5, min_center)) 
    angle = r.randint(0, 360)
    startAngle = 0
    endAngle = 360
    ellipse = cv2.ellipse(ellipse, center, axesLength, angle, startAngle, endAngle, Shapes.random_color(), -1)
    return ellipse
  @staticmethod
  def dist(p,q):
    return ((p[0]-q[0])**2 + (p[1]-q[1])**2)**0.5
  @staticmethod
  def create_triangle():
    triangle = np.zeros((50, 50, 3), np.uint8)
    boolean = True
    while(boolean):
      p1 = [r.randint(0, 20), r.randint(0, 50)]
      p2 = [r.randint(0, 25), r.randint(0, 25)]
      p3 = [r.randint(35, 50), r.randint(0, 50)]
      boolean = ((p1[0] * (p2[1] - p3[1]) + p2[0] * (p3[1] - p1[1]) + p3[0] * (p1[1] - p2[1]) ) / 2) < 100 # verify area of triangle to avoid wierd line shapes
    points = np.array([p1, p2, p3])
    color = Shapes.random_color()
    triangle = cv2.fillConvexPoly(triangle, points, color)
    triangle = cv2.line(triangle, tuple(p1), tuple(p2), color, 1) 
    triangle = cv2.line(triangle, tuple(p2), tuple(p3), color, 1) 
    triangle = cv2.line(triangle, tuple(p3), tuple(p1), color, 1) 
    return triangle  

  @staticmethod
  def create_rect():
    start_point = (r.randint(10,30), r.randint(10,30))
    h = r.randint(8,50-start_point[0])
    w = r.randint(8,50-start_point[1])  
    end_point = (h+start_point[0],w+start_point[1])
    thickness = -1
    rect = np.zeros((50, 50, 3)).astype(np.uint8)
    rect = cv2.rectangle(rect, start_point, end_point, Shapes.random_color(), thickness)
    return rect 

  @staticmethod
  def random_color():
    return [r.randint(10,255),r.randint(10,255),r.randint(10,255)]
  @staticmethod
  def create_by_category():
    """
    0 - Rectangle
    1 - Triangle
    2 - Ellipse
    """ 
    categories = [ [],[],[] ]
    for i in range(10):
      rect = Shapes.create_rect()
      tri = Shapes.create_triangle()
      elipse = Shapes.create_ellipse()
      categories[0].append(rect)
      categories[1].append(tri)
      categories[2].append(elipse)
    return categories

  @staticmethod
  def load_data(test_percentage=0.2):
    augmented_dataset = [[],[],[]]
    labels = ["Rectangle", "Triangle", "Ellipse"]
    data_set = Shapes.create_by_category()

    for index in range (3):
      for img in data_set[index]:
        current_augmentations = RandomAugmentation.generate_augmentations(img, 4, 0.5)
        augmented_dataset[index].append(img)
        for img2 in current_augmentations:
          augmented_dataset[index].append(img2)
          
    x_train = []
    x_test = []

    y_train = []
    y_test = []
    count = 0
    for i in range(3):
      for example in augmented_dataset[i]:
        if count < len(augmented_dataset[i]) * (1-test_percentage):

          x_train.append(example)
          y_train.append(labels[i])

        else:
          x_test.append(example)
          y_test.append(labels[i])

    x_train = np.asarray(x_train)
    y_train = np.asarray(y_train)

    x_test = np.asarray(x_test)
    y_test = np.asarray(y_test)


    return (x_train, y_train), (x_test, y_test)
In [38]:
data_set = Shapes.create_by_category()
rect_labels = ["Rectangle" for i in range(5)]
triangle_labels = ["Triangle" for i in range(5)]
ellipse_labels = ["Ellipse" for i in range(5)]
Image.show_all(data_set[0][0:5]    , rect_labels)
Image.show_all(data_set[1][0:5], triangle_labels)
Image.show_all(data_set[2][0:5], ellipse_labels)
In [40]:
(x_train, y_train), (x_test, y_test) = Shapes.load_data(0) #chose 100% of the examples into the x_train y_train.
for i in range(0,len(x_train),5):
  Image.show_all(x_train[i:i+5]    , y_train[i:i+5])